3 use MediaWiki\Block\BlockManager
;
4 use MediaWiki\Block\DatabaseBlock
;
5 use MediaWiki\Block\CompositeBlock
;
6 use MediaWiki\Block\SystemBlock
;
7 use MediaWiki\Config\ServiceOptions
;
8 use MediaWiki\MediaWikiServices
;
9 use Wikimedia\TestingAccessWrapper
;
14 * @coversDefaultClass \MediaWiki\Block\BlockManager
16 class BlockManagerTest
extends MediaWikiTestCase
{
24 protected function setUp() {
27 $this->user
= $this->getTestUser()->getUser();
28 $this->sysopId
= $this->getTestSysop()->getUser()->getId();
29 $this->blockManagerConfig
= [
30 'wgApplyIpBlocksToXff' => true,
31 'wgCookieSetOnAutoblock' => true,
32 'wgCookieSetOnIpBlock' => true,
33 'wgDnsBlacklistUrls' => [],
34 'wgEnableDnsBlacklist' => true,
36 'wgProxyWhitelist' => [],
37 'wgSecretKey' => false,
38 'wgSoftBlockRanges' => [],
42 private function getBlockManager( $overrideConfig ) {
43 return new BlockManager(
44 ...$this->getBlockManagerConstructorArgs( $overrideConfig )
48 private function getBlockManagerConstructorArgs( $overrideConfig ) {
49 $blockManagerConfig = array_merge( $this->blockManagerConfig
, $overrideConfig );
50 $this->setMwGlobals( $blockManagerConfig );
51 $this->overrideMwServices();
54 BlockManager
::$constructorOptions,
55 MediaWikiServices
::getInstance()->getMainConfig()
58 $this->user
->getRequest()
63 * @dataProvider provideGetBlockFromCookieValue
64 * @covers ::getBlockFromCookieValue
65 * @covers ::shouldApplyCookieBlock
67 public function testGetBlockFromCookieValue( $options, $expected ) {
68 $blockManager = TestingAccessWrapper
::newFromObject(
69 $this->getBlockManager( [
70 'wgCookieSetOnAutoblock' => true,
71 'wgCookieSetOnIpBlock' => true,
75 $block = new DatabaseBlock( array_merge( [
76 'address' => $options['target'] ?
: $this->user
,
77 'by' => $this->sysopId
,
78 ], $options['blockOptions'] ) );
81 $user = $options['loggedIn'] ?
$this->user
: new User();
82 $user->getRequest()->setCookie( 'BlockID', $block->getCookieValue() );
84 $this->assertSame( $expected, (bool)$blockManager->getBlockFromCookieValue(
92 public static function provideGetBlockFromCookieValue() {
94 'Autoblocking user block' => [
99 'enableAutoblock' => true
104 'Non-autoblocking user block' => [
108 'blockOptions' => [],
112 'IP block for anonymous user' => [
114 'target' => '127.0.0.1',
116 'blockOptions' => [],
120 'IP block for logged in user' => [
122 'target' => '127.0.0.1',
124 'blockOptions' => [],
128 'IP range block for anonymous user' => [
130 'target' => '127.0.0.0/8',
132 'blockOptions' => [],
140 * @dataProvider provideIsLocallyBlockedProxy
141 * @covers ::isLocallyBlockedProxy
143 public function testIsLocallyBlockedProxy( $proxyList, $expected ) {
144 $blockManager = TestingAccessWrapper
::newFromObject(
145 $this->getBlockManager( [
146 'wgProxyList' => $proxyList
151 $this->assertSame( $expected, $blockManager->isLocallyBlockedProxy( $ip ) );
154 public static function provideIsLocallyBlockedProxy() {
156 'Proxy list is empty' => [ [], false ],
157 'Proxy list contains IP' => [ [ '1.2.3.4' ], true ],
158 'Proxy list contains IP as value' => [ [ 'test' => '1.2.3.4' ], true ],
159 'Proxy list contains range that covers IP' => [ [ '1.2.3.0/16' ], true ],
164 * @dataProvider provideIsDnsBlacklisted
165 * @covers ::isDnsBlacklisted
166 * @covers ::inDnsBlacklist
168 public function testIsDnsBlacklisted( $options, $expected ) {
169 $blockManagerConfig = [
170 'wgEnableDnsBlacklist' => true,
171 'wgDnsBlacklistUrls' => $options['blacklist'],
172 'wgProxyWhitelist' => $options['whitelist'],
175 $blockManager = $this->getMockBuilder( BlockManager
::class )
176 ->setConstructorArgs( $this->getBlockManagerConstructorArgs( $blockManagerConfig ) )
177 ->setMethods( [ 'checkHost' ] )
179 $blockManager->method( 'checkHost' )
180 ->will( $this->returnValueMap( [ [
181 $options['dnsblQuery'],
182 $options['dnsblResponse'],
187 $blockManager->isDnsBlacklisted( $options['ip'], $options['checkWhitelist'] )
191 public static function provideIsDnsBlacklisted() {
192 $dnsblFound = [ '127.0.0.2' ];
193 $dnsblNotFound = false;
195 'IP is blacklisted' => [
197 'blacklist' => [ 'dnsbl.test' ],
199 'dnsblQuery' => '1.0.0.127.dnsbl.test',
200 'dnsblResponse' => $dnsblFound,
202 'checkWhitelist' => false,
206 'IP is blacklisted; blacklist has key' => [
208 'blacklist' => [ [ 'dnsbl.test', 'key' ] ],
210 'dnsblQuery' => 'key.1.0.0.127.dnsbl.test',
211 'dnsblResponse' => $dnsblFound,
213 'checkWhitelist' => false,
217 'IP is blacklisted; blacklist is array' => [
219 'blacklist' => [ [ 'dnsbl.test' ] ],
221 'dnsblQuery' => '1.0.0.127.dnsbl.test',
222 'dnsblResponse' => $dnsblFound,
224 'checkWhitelist' => false,
228 'IP is not blacklisted' => [
230 'blacklist' => [ 'dnsbl.test' ],
232 'dnsblQuery' => '4.3.2.1.dnsbl.test',
233 'dnsblResponse' => $dnsblNotFound,
235 'checkWhitelist' => false,
239 'Blacklist is empty' => [
243 'dnsblQuery' => '1.0.0.127.dnsbl.test',
244 'dnsblResponse' => $dnsblFound,
246 'checkWhitelist' => false,
250 'IP is blacklisted and whitelisted; whitelist is not checked' => [
252 'blacklist' => [ 'dnsbl.test' ],
254 'dnsblQuery' => '1.0.0.127.dnsbl.test',
255 'dnsblResponse' => $dnsblFound,
256 'whitelist' => [ '127.0.0.1' ],
257 'checkWhitelist' => false,
261 'IP is blacklisted and whitelisted; whitelist is checked' => [
263 'blacklist' => [ 'dnsbl.test' ],
265 'dnsblQuery' => '1.0.0.127.dnsbl.test',
266 'dnsblResponse' => $dnsblFound,
267 'whitelist' => [ '127.0.0.1' ],
268 'checkWhitelist' => true,
276 * @covers ::getUniqueBlocks
278 public function testGetUniqueBlocks() {
281 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
283 $block = $this->getMockBuilder( DatabaseBlock
::class )
284 ->setMethods( [ 'getId' ] )
286 $block->method( 'getId' )
287 ->willReturn( $blockId );
289 $autoblock = $this->getMockBuilder( DatabaseBlock
::class )
290 ->setMethods( [ 'getParentBlockId', 'getType' ] )
292 $autoblock->method( 'getParentBlockId' )
293 ->willReturn( $blockId );
294 $autoblock->method( 'getType' )
295 ->willReturn( DatabaseBlock
::TYPE_AUTO
);
297 $blocks = [ $block, $block, $autoblock, new SystemBlock() ];
299 $this->assertSame( 2, count( $blockManager->getUniqueBlocks( $blocks ) ) );
303 * @dataProvider provideTrackBlockWithCookie
304 * @covers ::trackBlockWithCookie
306 public function testTrackBlockWithCookie( $options, $expectedVal ) {
307 $this->setMwGlobals( 'wgCookiePrefix', '' );
309 $request = new FauxRequest();
310 if ( $options['cookieSet'] ) {
311 $request->setCookie( 'BlockID', 'the value does not matter' );
314 $user = $this->getMockBuilder( User
::class )
315 ->setMethods( [ 'getBlock', 'getRequest' ] )
317 $user->method( 'getBlock' )
318 ->willReturn( $options['block'] );
319 $user->method( 'getRequest' )
320 ->willReturn( $request );
322 // Although the block cookie is set via DeferredUpdates, in command line mode updates are
323 // processed immediately
324 $blockManager = $this->getBlockManager( [
326 'wgCookieSetOnIpBlock' => true,
328 $blockManager->trackBlockWithCookie( $user );
330 /** @var FauxResponse $response */
331 $response = $request->response();
332 $this->assertCount( $expectedVal ?
1 : 0, $response->getCookies() );
333 $this->assertEquals( $expectedVal ?
: null, $response->getCookie( 'BlockID' ) );
336 public function provideTrackBlockWithCookie() {
339 'Block cookie is already set; there is a trackable block' => [
342 'block' => $this->getTrackableBlock( $blockId ),
346 'Block cookie is already set; there is no block' => [
353 'Block cookie is not yet set; there is no block' => [
355 'cookieSet' => false,
360 'Block cookie is not yet set; there is a trackable block' => [
362 'cookieSet' => false,
363 'block' => $this->getTrackableBlock( $blockId ),
367 'Block cookie is not yet set; there is a composite block with a trackable block' => [
369 'cookieSet' => false,
370 'block' => new CompositeBlock( [
371 'originalBlocks' => [
373 $this->getTrackableBlock( $blockId ),
379 'Block cookie is not yet set; there is a composite block but no trackable block' => [
381 'cookieSet' => false,
382 'block' => new CompositeBlock( [
383 'originalBlocks' => [
394 private function getTrackableBlock( $blockId ) {
395 $block = $this->getMockBuilder( DatabaseBlock
::class )
396 ->setMethods( [ 'getType', 'getId' ] )
398 $block->method( 'getType' )
399 ->willReturn( DatabaseBlock
::TYPE_IP
);
400 $block->method( 'getId' )
401 ->willReturn( $blockId );
406 * @dataProvider provideSetBlockCookie
407 * @covers ::setBlockCookie
409 public function testSetBlockCookie( $expiryDelta, $expectedExpiryDelta ) {
410 $this->setMwGlobals( [
411 'wgCookiePrefix' => '',
414 $request = new FauxRequest();
415 $response = $request->response();
417 $blockManager = $this->getBlockManager( [
419 'wgCookieSetOnIpBlock' => true,
422 $now = wfTimestamp();
424 $block = new DatabaseBlock( [
425 'expiry' => $expiryDelta === '' ?
'' : $now +
$expiryDelta
427 $blockManager->setBlockCookie( $block, $response );
428 $cookies = $response->getCookies();
431 $now +
$expectedExpiryDelta,
432 $cookies['BlockID']['expire'],
434 60 // Allow actual to be up to 60 seconds later than expected
438 public static function provideSetBlockCookie() {
439 // Maximum length of a block cookie, defined in BlockManager::setBlockCookie
440 $maxExpiryDelta = ( 24 * 60 * 60 );
442 $longExpiryDelta = ( 48 * 60 * 60 );
443 $shortExpiryDelta = ( 12 * 60 * 60 );
446 'Block has indefinite expiry' => [
450 'Block expiry is later than maximum cookie block expiry' => [
454 'Block expiry is sooner than maximum cookie block expiry' => [
462 * @covers ::shouldTrackBlockWithCookie
464 public function testShouldTrackBlockWithCookieSystemBlock() {
465 $blockManager = TestingAccessWrapper
::newFromObject( $this->getBlockManager( [] ) );
466 $this->assertFalse( $blockManager->shouldTrackBlockWithCookie(
473 * @dataProvider provideShouldTrackBlockWithCookie
474 * @covers ::shouldTrackBlockWithCookie
476 public function testShouldTrackBlockWithCookie( $options, $expected ) {
477 $block = $this->getMockBuilder( DatabaseBlock
::class )
478 ->setMethods( [ 'getType', 'isAutoblocking' ] )
480 $block->method( 'getType' )
481 ->willReturn( $options['type'] );
482 if ( isset( $options['autoblocking'] ) ) {
483 $block->method( 'isAutoblocking' )
484 ->willReturn( $options['autoblocking'] );
487 $blockManager = TestingAccessWrapper
::newFromObject(
488 $this->getBlockManager( $options['blockManagerConfig'] )
493 $blockManager->shouldTrackBlockWithCookie( $block, $options['isAnon'] )
497 public static function provideShouldTrackBlockWithCookie() {
499 'IP block, anonymous user, IP block cookies enabled' => [
501 'type' => DatabaseBlock
::TYPE_IP
,
503 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
507 'IP range block, anonymous user, IP block cookies enabled' => [
509 'type' => DatabaseBlock
::TYPE_RANGE
,
511 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
515 'IP block, anonymous user, IP block cookies disabled' => [
517 'type' => DatabaseBlock
::TYPE_IP
,
519 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => false ],
523 'IP block, logged in user, IP block cookies enabled' => [
525 'type' => DatabaseBlock
::TYPE_IP
,
527 'blockManagerConfig' => [ 'wgCookieSetOnIpBlock' => true ],
531 'User block, anonymous, autoblock cookies enabled, block is autoblocking' => [
533 'type' => DatabaseBlock
::TYPE_USER
,
535 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
536 'autoblocking' => true,
540 'User block, logged in, autoblock cookies enabled, block is autoblocking' => [
542 'type' => DatabaseBlock
::TYPE_USER
,
544 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
545 'autoblocking' => true,
549 'User block, logged in, autoblock cookies disabled, block is autoblocking' => [
551 'type' => DatabaseBlock
::TYPE_USER
,
553 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => false ],
554 'autoblocking' => true,
558 'User block, logged in, autoblock cookies enabled, block is not autoblocking' => [
560 'type' => DatabaseBlock
::TYPE_USER
,
562 'blockManagerConfig' => [ 'wgCookieSetOnAutoblock' => true ],
563 'autoblocking' => false,
567 'Block type is autoblock' => [
569 'type' => DatabaseBlock
::TYPE_AUTO
,
571 'blockManagerConfig' => [],
579 * @covers ::clearBlockCookie
581 public function testClearBlockCookie() {
582 $this->setMwGlobals( [
583 'wgCookiePrefix' => '',
586 $request = new FauxRequest();
587 $response = $request->response();
588 $response->setCookie( 'BlockID', '100' );
589 $this->assertSame( '100', $response->getCookie( 'BlockID' ) );
591 BlockManager
::clearBlockCookie( $response );
592 $this->assertSame( '', $response->getCookie( 'BlockID' ) );
596 * @dataProvider provideGetIdFromCookieValue
597 * @covers ::getIdFromCookieValue
599 public function testGetIdFromCookieValue( $options, $expected ) {
600 $blockManager = $this->getBlockManager( [
601 'wgSecretKey' => $options['secretKey']
605 $blockManager->getIdFromCookieValue( $options['cookieValue'] )
609 public static function provideGetIdFromCookieValue() {
612 $hmac = MWCryptHash
::hmac( $blockId, $secretKey, false );
614 'No secret key is set' => [
617 'cookieValue' => $blockId,
618 'calculatedHmac' => MWCryptHash
::hmac( $blockId, '', false ),
622 'Secret key is set and stored hmac is correct' => [
624 'secretKey' => $secretKey,
625 'cookieValue' => $blockId . '!' . $hmac,
626 'calculatedHmac' => $hmac,
630 'Secret key is set and stored hmac is incorrect' => [
632 'secretKey' => $secretKey,
633 'cookieValue' => $blockId . '!xyz',
634 'calculatedHmac' => $hmac,
642 * @dataProvider provideGetCookieValue
643 * @covers ::getCookieValue
645 public function testGetCookieValue( $options, $expected ) {
646 $blockManager = $this->getBlockManager( [
647 'wgSecretKey' => $options['secretKey']
650 $block = $this->getMockBuilder( DatabaseBlock
::class )
651 ->setMethods( [ 'getId' ] )
653 $block->method( 'getId' )
654 ->willReturn( $options['blockId'] );
658 $blockManager->getCookieValue( $block )
662 public static function provideGetCookieValue() {
665 'Secret key not set' => [
668 'blockId' => $blockId,
669 'hmac' => MWCryptHash
::hmac( $blockId, '', false ),
673 'Secret key set' => [
675 'secretKey' => '123',
676 'blockId' => $blockId,
677 'hmac' => MWCryptHash
::hmac( $blockId, '123', false ),
679 $blockId . '!' . MWCryptHash
::hmac( $blockId, '123', false ) ],